Skip to content

Custom Scripts

(Optional lesson)

NPM scripts are the perfect place to define small tasks that need to be executed from time to time.

For example, maybe you've decided to use a tool like Cypress to create end-to-end tests. The command to run Cypress is cypress run, but we can skip having to remember this by creating a new NPM script:

{
"scripts": {
"test": "cypress run"
}
}

I like this convention, because it means all of my projects share the same test command (npm run test) even if they use different testing tools.

NPM scripts become super powerful when we take advantage of some CLI logic.

Pre and post scripts

Sometimes, I want to perform some action before another script runs.

For example, on my blog at joshwcomeau.com, I wrote a Node script that generates the RSS feed's XML file, so that people who use RSS readers can read my new posts.

In order to make sure that the RSS feed is always up-to-date, I want to re-generate the XML file whenever I build the site. I can do this with a “prebuild” script:

{
"scripts": {
"prebuild": "node ./build-helpers/generate-rss-feed.js",
"build": "next build",
}
}

By using the pre prefix, I ensure that the prebuild script will run before the build script. When I run npm run build, NPM will first run the prebuild script, then the build script.

We can do the same thing with the post prefix, if we want a script to run afterwards. And it works for any script name, not just build.

For example:

{
"scripts": {
"party": "echo 'Party time!'",
"preparty": "echo '3… 2… 1…'",
"postparty": "echo '🥳'"
}
}

When we run this script, we see the following output:

$ npm run party
321
Party time!
🥳

Chaining scripts

Alright, we're getting a bit in the weeds here, but I want to share one more special trick I use with NPM scripts.

Sometimes, I want multiple things to happen before a script runs. On my blog, I need to generate the RSS feed, but I also want to build a sitemap for SEO, and generate social images for Twitter/Facebook.

Here's how I set it up:

{
"scripts": {
"build:rss": "node ./build-helpers/generate-rss-feed.js",
"build:sitemap": "node ./build-helpers/generate-sitemap.js",
"build:og-images": "node ./build-helpers/generate-og-images.js",
"prebuild": "npm run build:og-images && npm run build:sitemap && npm run build:rss",
"build": "next build",
}
}

I've created 3 individual scripts: build:rss, build:sitemap, and build:og-images. The colon (:) is a convention, it has no functional effect. I use it to make clear that these tasks are all related to building.

In the prebuild script, I run each of these 3 scripts in turn, with the && operator. Like in JavaScript, the && operator allows us to chain multiple expressions together. Check out the “Logical Operators” lesson 👀 for more context.

When I run npm run build, the prebuild script is automatically fired, which calls the 3 scripts in turn. Once all 3 have finished, the regular build operation runs, building my site for production.